/* 

   A set of functions for parsing and manipulating input command
   lines for a shell. Most of the string parsing and interpreting
   happens in this file.

*/

// Included Libraries and Files
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#include"parsecmd.h"

//#define DEBUG 5


/* 
   A wrapper function around malloc that checks the return
   value and exits if something goes wrong.

   Inputs: size_t size - amount of memory to pass to malloc()
   Returns: Pointer to allocated block.
*/
void * Malloc( size_t size ) {

  void * toRet = malloc(size);
  if ( toRet ) {
    return toRet;
  }
  perror("malloc");
  exit(1);

}
/* 
 * Adds spaces before and after special characters to ensure that 
 * assumptions made in later functions hold true. Specifically, adds
 * spaces before and after a |, &, and < character, since these characters
 * may not be separated by arguments.
 *
 * Inputs: char * cmd - original command to preparse.
 * Returns: char * toRet - manipulated command with spaces added.
 */
char * preParser( char * cmd ) {

  // Add spaces between any pipe characters and any I/o input characters.
  char * iter;
  char * toRet = cmd;
  if ( (iter = strchr( cmd, '|' ) ) ) {
    *iter = '\0';
    // Space for first string, second string, token with spaces,
    // and a null terminator. 
    toRet = Malloc( (strlen( cmd ) + strlen( iter + 1 ) + 3 + 1) 
        * sizeof(char) );
    strncpy( toRet, cmd, strlen(cmd) + 1 );
    strcat( toRet, " | ");
    strcat( toRet, iter+1 );
    free( cmd );
    cmd = toRet;
  }

  if ( ( iter = strchr( cmd, '&' ) ) ) {
    *iter = '\0';
    toRet = Malloc( (strlen( cmd ) + strlen( iter + 1 ) + 3 + 1) 
        * sizeof(char) );
    strncpy( toRet, cmd, strlen(cmd) + 1 );
    strcat( toRet, " & ");
    strcat( toRet, iter+1 );
    free( cmd );
    cmd = toRet;
  }

  if ( ( iter = strchr( cmd, '<' ) ) ) {
    *iter = '\0';
    toRet = Malloc( (strlen( cmd ) + strlen( iter + 1 ) + 3 + 1) 
        * sizeof(char) );
    strncpy( toRet, cmd, strlen(cmd) + 1 );
    strcat( toRet, " < ");
    strcat( toRet, iter+1 );
    free( cmd );
  }
  
  return toRet;


}

/*
 * Checks to see if the command line is valid by checking for 
 * only spaces, singleton redirection characters, invalid redirect
 * characters, unmatched piped commands.
 *
 * Returns 1 if there are errors, 0 otherwise
 */
int areErrors( char ** argv ) {

  if ( ! argv[0] ) {
    // Empty command with only spaces
    return 1;

  }

  // Check for a singelton '&', '|', or redirect character
  if ( ! strcmp( argv[0], "&" ) ||
       ! strcmp( argv[0], "|" ) ||
       ! strcmp( argv[0], "1>" ) ||
       ! strcmp( argv[0], "2>" ) ||
       ! strcmp( argv[0], "<" ) ) {
    return 1;
  }

  // Check for invalid redirect characters
  int idx = 0;
  while ( argv[idx] ) {
    if ( strchr( argv[idx], '>' ) ) {
      if ( strcmp( argv[idx], "1>" ) &&
           strcmp( argv[idx], "2>" ) ) {
        return 1;
      }
    }
    if ( strchr( argv[idx], '<' ) ) {
      if ( strcmp( argv[idx], "<" ) ) {
        return 1;
      }
    }
    idx ++;
  }

  // Check for pipes or redirection characters at the end of argv
  // that are not piping to / redirecting anything
  idx = 0;
  while ( argv[idx] ) {
    idx ++;
  }
  idx --;

  if ( ! strcmp( argv[idx], "|" ) ||
       ! strcmp( argv[idx], "1>" ) ||
       ! strcmp( argv[idx], "2>" ) ||
       ! strcmp( argv[idx], "<" ) ) {
    return 1;
  }
  return 0;
}


/*
  Checks to see if the entered path is a full path (that does
  not require any further work) or just the name of an executable
  (in which case we need to search the PATH environment variable

  Returns 0 if searching PATH is necessary and 1 if searching 
  PATH is unnecessary
*/
int isFullPath( char * path ) {

  if ( path[0] == '.' || path[0] == '/' ) {
    return 1;
  }
  return 0;
}

/* 
   Splits an input string into argv buckets, returning a dynamically
   allocated NULL terminated array of char pointers.

   Inputs: char * line - the input string to split up
           char * delim - the input string to split on (for strtok)

   Returns: Dynamically allocated NULL terminated argv array.

*/
char ** split( char * line, char * delim ) {

  // Copy over command line, since strtok modifies it
  char * copy = Malloc ( sizeof(char) * (strlen( line ) + 1) );
  strncpy( copy, line, strlen(line) + 1 );

  // Count the number of buckets we will need
  int num = 0;
  char * tmp = copy;
  while ( strtok( tmp, delim ) ) {
    tmp = NULL;
    num ++;
  }
  num ++; // Need a NULL termination
#ifdef DEBUG
  printf("Counted %d objects, including NULL terminator\n", num);
#endif
  // Done with copy
  free ( copy );

  // Create argv array to return
  char ** toRet = Malloc( num * sizeof(char*) );

  // Tokenize passed in string, assigning to appropriate buckets
  tmp = line;
  int idx = 0;
  while ( (toRet[idx++] = strtok( tmp, delim )) ) {
    tmp = NULL;
  }
#ifdef DEBUG
  if ( ! toRet ) {
    printf("NULL toRet \n");
  }
  else {
    for ( idx = 0; idx < num; idx++ ) {
      printf("Argv[%d]: %s\n", idx, toRet[idx] );
    }
  }
#endif

  // Return the argv array
  return toRet;

}

/*
  Checks to see if an argv array is a piped command. If it is,
  returns a pointer to the beginning of the second command's 
  argv array, and NULL terminates the first command's argv by 
  overwriting the "|" bucket.

  Inputs: char ** argv - argv to check for pipes
  Returns: char ** pointer to second command's argv, or NULL
           if this is not a piped command.
*/
char *** handlePipes( char ** argv, int * num ) {

  int idx = 0;
  int numPipes = 0;
  int cmdNo;
  while ( argv[idx] ) {
    if ( ! strcmp( argv[idx], "|" ) ) {
      numPipes ++;
    }
    idx ++;
  }
  *num = numPipes + 1;

  char *** toRet = Malloc( (numPipes + 1) * sizeof( char* ) );
  idx = 0;
 
  toRet [ 0 ] = argv;
  cmdNo = 1;
  while ( argv[idx] ) {
    if ( ! strcmp( argv[idx], "|" ) ) {
      argv[idx] = NULL;
      toRet[ cmdNo ] = &argv[idx + 1];
      idx += 1;
      cmdNo ++;
    }
    idx ++;
  }
  
#ifdef DEBUG
  int i; 
  for ( i = 0; i < numPipes + 1; i ++ ) {
    printf("Command Number: %d\n", i);
    char ** currArgv = toRet [ i ];
    idx = 0;
    while ( currArgv[ idx ] ) {
      printf("\t%d: %s\n", idx, currArgv[idx] );
      idx ++;
    }
  }

#endif

  return toRet; 

}

/* 
   Checks a given argv to see if the command should be run in the
   background.

   Note: Since files can contain ampersands in the filenames, a 
   simple strchr is unsafe. We therefore assume that the ampersand
   will occur on its own. An alternate assumption might assume that
   filenames will not contain an &, and use strchr instead.

   Inputs: char ** argv - argv to check for background marker
   Returns: 1 if command should be run in background; 0 otherwise

*/
int isBG( char ** argv ) {

  int idx = 0;
  while ( argv[idx] && strcmp( argv[idx], "&" ) ) {
    idx ++ ;
  }
  if ( argv[idx] ) {
    // Found a single ampersand
    argv[idx] = NULL;
    return 1;
  }
  // Run in foreground
  return 0;
  

}


/* Iterates through the argv array, extracting filenames
 * for I/O redirection. NULL terminates the argv array at
 * the earliest I/O redirect character ( < or > ). The
 * files are opened in the appropriate mode and the appropriate
 * file descriptor passed by reference is set.
 *
 * Returns 0 if all open operations succeeded; 1 otherwise.
 */
int ioRedirect( char ** argv, int * infd, int * outfd, int * errfd ) {

  int idx = 0;
  int toRet = 0;
  *infd = 0;
  *outfd = 0;
  *errfd = 0;

  // Iterate through entire array (handle multiple redirections at
  // once.
  while ( argv[idx] ) {

    // Redirect STDOUT
    if ( !strcmp( argv[idx], "1>" ) ) {
      if ( argv[idx+1] ) {
        if ( (*outfd = open( argv[idx+1], 
			     O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR )) < 0 ) {
          perror("open");
      	  toRet = -1;
        }
        argv[idx+1] = NULL;
        argv[idx] = NULL;
        idx += 2;
      }
      else {
        printf("Error: Invalid syntax. Require file argument for I/O redirect\n");
        toRet = -1;
        argv[idx++] = NULL;
      }
    }


    // Redirect STDERR
    else if ( ! strcmp( argv[idx], "2>" ) ) {
      if ( argv[idx+1] ) {
        if ( (*errfd = 
	      open( argv[idx+1], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR )) 
	     < 0 ) {
	  perror("open");
          toRet = -1;
        }
        argv[idx+1] = NULL;
        argv[idx] = NULL;
        idx += 2;
      }
      else {
        printf("Error: Invalid syntax. ");
	printf("Require file argument for I/O redirect\n");
        toRet = -1;
        argv[idx++] = NULL;
      }
    }

    // Redirect STDIN
    else if ( ! strcmp( argv[idx], "<" ) ) {
      if ( argv[idx+1] ) {
        if ( (*infd = open( argv[idx+1], O_RDONLY )) < 0 ) {
	  perror("open");
          toRet = -1;
        }
        argv[idx+1] = NULL;
        argv[idx] = NULL;
        idx += 2;
      }
      else {
        printf("Error: Invalid syntax. ");
	printf("Require file argument for I/O redirect\n");
        toRet = -1;
        argv[idx++] = NULL;
      }
    }

    else { 
      idx ++ ;
    }
    fflush(stdout);
  }


  // Returns -1 if something bad happened
  return toRet;

}



/* Searches the path for the executable in exec.
   Returns a pointer to a dynamically allocated
   string with the full path to the executable, or
   NULL if no such path is found.
*/
char * getPath( char * path, char * exec ) {

  
  char * pathCpy = Malloc( (strlen(path) + 1) * sizeof(char) );
  
  strncpy( pathCpy, path, strlen(path) + 1 );
  
  char * iter = pathCpy;

  while ( (iter = strtok( iter, ":" ) ) ) {
    // Concatenate iter with the executable and a slash
    char * fullPath = 
      Malloc( (strlen(iter) + 1 + strlen(exec) + 1 ) * sizeof(char) );
    strncpy( fullPath, iter, strlen(iter)+1 );
    strncat( fullPath, "/", 1 );
    strncat( fullPath, exec, strlen(exec) + 1);
#ifdef DEBUG
    printf("Trying full path: %s\n", fullPath);
#endif
    if ( access( fullPath, X_OK ) ) {
      free( fullPath );
    }
    else {
#ifdef DEBUG
      printf("Returning path: %s\n", fullPath);
#endif
      free( pathCpy );
      return fullPath;
      
    }
    iter = NULL;
  }
  
  free( pathCpy );
#ifdef DEBUG
  printf("Returning NULL\n");
#endif
  return NULL;
}
